﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using IVideo;
using VideoConfig;
using System.Reflection;
using System.Threading;
using IConfig;
using IScript;
using IPlayer;
using System.Diagnostics;
using System.Runtime.InteropServices;
using IAlarm;
using AlarmManager;
using VideoFile;
using System.IO;

namespace Video
{
    /*  智能预测：
     *      不用实时从设备读取角度，根据云台的运动速度和已知角度，预测云台运动时的实时角度
     *      不用从设备读取镜头放大分辨率，当调节镜头倍率时，根据视频画面的变化分析当前放大倍率
     *      实时场景的三维重建
     *      行人及车辆的距离估计，行为分析
     *      烟火识别
     */

    /*  问题总结：
     *      透明通道：
     *          问题描述
     *              同一编码器的多路视频只能共用一个透明通道，且先开启的视频先占用，后开启的视频将无法使用。
     *          解决办法：
     *              1.对【IP相同】【通道号不同】的视频，共用一个透明通道，
     *                  对透明通道接收的消息通过【地址位】区分，对发送的消息进行排队发送。
     *              2.对不使用透明通道的视频及时释放通道，交替式使用。（经常开关可能导致硬件失效）
     *          目前办法：
     *              先到先得，或通过配置文件，一个编码器只允许其中一路使用透明通道
     *      角度同步：由于角度的获取需要通过透明通道向前端发送数据，
     *          而透明通道数据的发送会与通过编码器向云台发关的的控制命令相冲突，
     *          因此要保证角度的获取指令与云台的控制指定不冲突，两条指令之间的时间间隔至少在500毫秒
     *          由于存在这种问题，要实时从前端获取角度是个比较困难的问题
     *          解决办法：
     *              1.通过多次采样来估计云台的当前位置
     *                  分别在7种速度下控制云台运动10秒，然后停止
     *                  （例如：采样 第0秒 向左 第1秒 采样 第2秒 采样 第3秒 ... 第9秒 采样 第10秒 停止）
     *                  假设云台T秒后达到稳定速度，且一接收到停止指令立即停止
     *                  将第0秒至第10秒的采样结果分别记为S0,S1...S10
     *                  则平均角速度Va = ((S2-S1)+(S3-S2)+...+(S10-S9))/9
     *                  第0秒至第1秒为加速运动，其运动距离记为D1，运动时间记为T
     *                  则第1秒内的加速度为A1 = Math.Sqrt(2*D1/T)，其中T=1
     *                  利用Va,A1,D1进行预测，并校正这几个变量：
     *                      当云台运动时间t<1时，预测云台运动角度为D'=t*A1*A1/2
     *                      当t>=1时，预测云台运动角度为D'=S1+Va*t;
     *                  利用A0,S0,Va多次进行预测，例如：
     *                      1.采样 第0秒 向左 第20秒 停止，则实际值为D = S20-S0，预测值为D'=S1+Va*20
     *                          对Va进行调整，当D'>D时，减小Va，当D'<D时，增加Va，直到|D-D'|<Sigma
     *                      2.采样 第0秒 向左 第500毫秒 停止 则实际值D = S500-S0，预测值为D'=t*A1*A1/2，
     *                          对A1进行调整，当D'>D时，减小A1，当D'<D时，增加A1，直到|D-D'|<Sigma
     *          目前办法：
     *              对以上算法采取了简化，假设云台从一开始就是匀速运动
     *              直接计算Va = ((S2-S1)+(S3-S2)+...+(S10-S9))/9
     *              预测值D=Va*t
     *              平均误差Sigma<3度
     *      烟火识别
     *          待解决。。。
     *      地图界面
     *          等解决。。。
     *      与上一版本界面融合
     *          待解决。。。
     */

    /// <summary>
    /// 视频控件基类
    /// </summary>
    public partial class VideoBase : UserControl, IVideoBase
    {
        #region 私有变量
        IAnalyze.IAnalyze analyze;
        IAnalyze.IAnalyzeStrategy analyzeStrategy;
        IScript.ICruiseAnalyzeScript cruise;

        byte[] message = new byte[7];//透明通道接收消息缓存
        int messageIndex = 0;



        bool enableTransfer = false;
        byte channel;

        string ip;
        ushort port;
        string user;
        string password;
        #endregion

        #region 保护变量
        /// <summary>
        /// 随机数生成器
        /// </summary>
        protected readonly Random random = new Random();

        /// <summary>
        /// 控件当前状态
        /// </summary>
        public StateType State { get; protected set; }

        IVideoSDK video;
        /// <summary>
        /// 视频模块
        /// </summary>
        protected IVideoSDK InnerVideo { get { return video; } }

        IPlayerSDK recordReader;
        IPlayerSDK player;
        /// <summary>
        /// 播放模块
        /// </summary>
        protected IPlayerSDK InnerPlayer { get { return player; } }

        IConfigInfo configInfo;
        /// <summary>
        /// 配置模块
        /// </summary>
        protected IConfigInfo ConfigInfo { get { return configInfo; } }

        IAlarmWriter alarmWriter;
        /// <summary>
        /// 报警模块
        /// </summary>
        protected IAlarmWriter AlarmWriter { get { return alarmWriter; } }
        #endregion

        #region 构造方法
        /// <summary>
        /// 构造函数
        /// </summary>
        public VideoBase()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
            InitializeComponent();
        }
        #endregion

        #region 静态方法
        /// <summary>
        /// 获取区域设备列表
        /// </summary>
        /// <returns></returns>
        public static Dictionary<string, List<string>> GetAreaDeviceList()
        {
            return FileConfigReader.SingleConfig.GetAreaDeviceList();
        }
        #endregion

        #region 私有方法
        protected int VideoWidth = 352;
        protected int VideoHeight = 288;

        private object CallFunction(string name, params object[] parameters)
        {
            try
            {
                Type t = this.GetType();

                MethodInfo mi = t.GetMethod(name);

                if (mi != null) return mi.Invoke(this, parameters);

                return null;
            }
            catch
            {
                Debug.WriteLine(this, "Call " + name + " failed!");
                return null;
            }

        }

        protected string CallParameter(string parameter)
        {
            return this.configInfo[parameter];
        }

        /// <summary>
        /// YV12 视频流
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        void DecodeCall(IntPtr buffer, int width, int height)
        {
            this.VideoWidth = width;
            this.VideoHeight = height;
            OnVideoDecode(buffer, width, height);

            this.analyzeStrategy.InputVideoStream(buffer, width, height);
        }

        protected virtual void OnVideoDecode(IntPtr buffer, int width, int height) { }

        void PaintCall(IntPtr hCD)
        {
            OnVideoPaint(new VideoPaintEventArgs(Graphics.FromHdc(hCD)));
        }

        //动态构建类型
        object CreateType(string assemblyFile, string typename)
        {
            try
            {
                Assembly videoAssembly = Assembly.LoadFrom(assemblyFile);
                Type videoType = videoAssembly.GetType(typename);
                ConstructorInfo videoConstructor = videoType.GetConstructor(new Type[0]);
                return videoConstructor.Invoke(null);
            }
            catch (Exception Ex)
            {
                throw new ApplicationException(string.Format("Load Assembly {0} Type {1} Failed!|CreateType", assemblyFile, typename), Ex);
            }
        }
        #endregion

        #region 公有方法
        /// <summary>
        /// 启动识别策略，该方法由巡航策略模块调用
        /// </summary>
        public void AnalyzeStrategy()
        {
            this.analyzeStrategy.SyncStartAnalyzeStrategy();
        }

        /// <summary>
        /// 以非解码模式开启视频
        /// </summary>
        /// <returns></returns>
        public bool OpenVideoOnly()
        {
            this.CheckLogined(true, "OpenVideo");

            if (this.video.IsOpened) return true;

            byte channel;
            if (byte.TryParse(this.configInfo["channel"], out channel))
                this.video.OpenVideo(channel, this.Handle);

            return this.video.IsOpened;
        }

        /// <summary>
        /// 以解码但不显示视频模式开启视频
        /// </summary>
        /// <returns></returns>
        public bool OpenVideoHide()
        {
            this.CheckLogined(true, "OpenVideo");

            if (this.video.IsOpened) return true;

            byte channel;
            if (byte.TryParse(this.configInfo["channel"], out channel))
                this.video.OpenVideoDecode(channel, IntPtr.Zero);

            return this.video.IsOpened;
        }
        #endregion

        #region 保护方法
        /// <summary>
        /// 视频绘图
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnVideoPaint(VideoPaintEventArgs e)
        {
            if (this.VideoPaint != null) this.VideoPaint(this, e);
        }
        #endregion

        #region 状态检测
        void CheckLogined(bool state, string function)
        {
            if (state && !this.video.IsLogined)
                throw new InvalidOperationException("User hasn't logined encoder!|" + function);
            else if (!state && this.video.IsLogined)
                throw new InvalidOperationException("User has logined encoder!|" + function);
        }

        void CheckOpened(bool state, string function)
        {
            if (state && !this.video.IsOpened)
                throw new InvalidOperationException("Video hasn't been opened!|" + function);
            else if (!state && this.video.IsOpened)
                throw new InvalidOperationException("Video has been opened!|" + function);
        }

        void CheckRecording(bool state, string function)
        {
            if (state && !this.video.IsRecording)
                throw new InvalidOperationException("Video recording hasn't been started!|" + function);
            else if (!state && this.video.IsRecording)
                throw new InvalidOperationException("Video recording has been started!|" + function);
        }

        void CheckTransfer485(bool state, string function)
        {
            if (state && !this.video.IsTransferOpened)
                throw new InvalidOperationException("Transfer 485 hasn't been opened!|" + function);
            else if (!state && this.video.IsTransferOpened)
                throw new InvalidOperationException("Transfer 485 has been opened!|" + function);
        }
        #endregion

        #region 报警事件
        protected GroupInfo group = null;

        void analyzeStrategy_AnalyzeBegin(object sender, IAnalyze.EventArgsAnalyzeBegin e)
        {
            //创建报警组
            group = new GroupInfo(e.GroupID, this.areaName, this.deviceName, this.Horizon, this.Vertical, e.AnalyzeCount);

            OnAnalyzeBegin(new AlarmEventArgs(this.group, null));
        }

        protected virtual void OnAnalyzeBegin(AlarmEventArgs e) { }

        void analyzeStrategy_AnalyzeAlarm(object sender, IAnalyze.EventArgsAnalyzeAlarm e)
        {
            if (this.group != null)
            {
                AlarmInfo alarm = new AlarmInfo(this.group.GroupTime, e.FrameIndex, e.AlarmTime, e.AlarmRegion, e.AlarmRegionType);

                //保存报警
                if (this.alarmWriter.WriteAlarm(this.group, alarm, e.AlarmFrame))
                {
                    //添加报警
                    if (this.group!= null) this.group.Add(alarm);
                    OnAnalyzeAlarm(new AlarmEventArgs(this.group, alarm), e.AlarmFrame);
                }
                e.AlarmFrame.Dispose();
            }
        }

        protected virtual void OnAnalyzeAlarm(AlarmEventArgs e, Bitmap frame)
        {
            if (this.Alarm != null) this.Alarm(this, e);
        }

        void analyzeStrategy_AnalyzeEnd(object sender, IAnalyze.EventArgsAnalyzeEnd e)
        {
            if (this.group != null && this.group.AlarmCount > 0)
            {
                //保存报警组
                this.alarmWriter.WriteGroup(this.group);

                OnAnalyzeEnd(new AlarmEventArgs(this.group, null));

                this.group = null;
            }
        }

        protected virtual void OnAnalyzeEnd(AlarmEventArgs e)
        {
            if (this.Alarm != null) this.Alarm(this, e);
        }

        /// <summary>
        /// 保存报警信息组
        /// </summary>
        /// <param name="group"></param>
        public void SaveGroupInfo(GroupInfo group)
        {
            this.alarmWriter.WriteGroup(group);
        }
        #endregion

        #region 公有属性



        #endregion

        #region IVideoBase 成员
        #region 公有事件
        /// <summary>
        /// 报警事件
        /// </summary>
        public event EventHandler<AlarmEventArgs> Alarm;

        /// <summary>
        /// 接收水平角事件
        /// </summary>
        public event EventHandler<HorizonEventArgs> ReceiveHorizon;

        /// <summary>
        /// 接收垂直角事件
        /// </summary>
        public event EventHandler<VerticalEventArgs> ReceiveVertical;

        /// <summary>
        /// 视频绘图事件
        /// </summary>
        public event EventHandler<VideoPaintEventArgs> VideoPaint;
        #endregion

        #region 状态属性
        bool isInitialized;
        /// <summary>
        /// 是否初始化
        /// </summary>
        [Browsable(false)]
        public bool IsInitialized
        {
            get { return this.isInitialized; }
        }

        /// <summary>
        /// 是否登陆编码器
        /// </summary>
        [Browsable(false)]
        public bool IsLogined
        {
            get { return this.video.IsLogined; }
        }

        /// <summary>
        /// 是否开启视频
        /// </summary>
        [Browsable(false)]
        public bool IsOpened
        {
            get { return this.video.IsOpened; }
        }

        /// <summary>
        /// 是否正在录像
        /// </summary>
        [Browsable(false)]
        public bool IsRecording
        {
            get { return this.video.IsRecording; }
        }

        /// <summary>
        /// 是否正在识别
        /// </summary>
        [Browsable(false)]
        public bool IsAnalyzing
        {
            get { return this.cruise.IsAnalyzing; }
        }

        /// <summary>
        /// 是否正在巡航
        /// </summary>
        [Browsable(false)]
        public bool IsCruising
        {
            get { return this.cruise.IsCruising; }
        }
        #endregion

        #region 公有属性
        protected string areaName;
        /// <summary>
        /// 区域名称
        /// </summary>
        [Browsable(false)]
        public string AreaName { get { return this.areaName; } }

        protected string deviceName;
        /// <summary>
        /// 设备名称
        /// </summary>
        [Browsable(false)]
        public string DeviceName { get { return this.deviceName; } }

        protected byte panTiltSpeed = 4;
        /// <summary>
        /// 云台速度
        /// </summary>
        [Browsable(false)]
        public byte PanTiltSpeed { get { return this.panTiltSpeed; } }

        protected short revise;
        /// <summary>
        /// 水平修正角
        ///     云台在正东方向时的角度
        /// </summary>
        [Browsable(false)]
        public short Revise { get { return this.revise; } }

        protected uint longitude;
        /// <summary>
        /// 经度
        /// </summary>
        [Browsable(false)]
        public uint Longitude { get { return this.longitude; } }

        protected uint latitude;
        /// <summary>
        /// 纬度
        /// </summary>
        [Browsable(false)]
        public uint Latitude { get { return latitude; } }

        protected uint high;
        /// <summary>
        /// 云台高度
        /// </summary>
        [Browsable(false)]
        public uint High { get { return high; } }
        #endregion

        #region 视频管理
        /// <summary>
        /// 初始化设备
        /// </summary>
        /// <param name="area">设备的区域名</param>
        /// <param name="device">设备的设备名</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool Initialize(string area, string device)
        {
            if (this.isInitialized) return true;

            //获取配置信息
            this.configInfo = FileConfigReader.SingleConfig[area, device];

            if (this.configInfo == null)
                throw new ApplicationException(string.Format("Get area {0} device {1} config failed!|Initialize", area, device));

            //动态加载播放模块
            this.player = this.CreateType(configInfo["PlayerFile"], configInfo["PlayerType"]) as IPlayer.IPlayerSDK;
            this.player.DecodeCallback = new DecodeCallback(DecodeCall);
            this.recordReader = this.CreateType(configInfo["PlayerFile"], configInfo["PlayerType"]) as IPlayer.IPlayerSDK;

            //动态加载视频模块
            this.video = this.CreateType(configInfo["VideoFile"], configInfo["VideoType"]) as IVideoSDK;
            this.video.RealStreamCallback = new RealStreamCallback(this.player.Open);
            this.video.PaintCallback = new IVideo.PaintCallback(this.PaintCall);

            //动态加载识别模块
            this.analyze = this.CreateType(configInfo["AnalyzeFile"], configInfo["AnalyzeType"]) as IAnalyze.IAnalyze;

            //报警保存
            this.alarmWriter = new FileAlarmWriter();

            //动态加载识别策略模块
            this.analyzeStrategy = this.CreateType(configInfo["AnalyzeStrategyFile"], configInfo["AnalyzeStrategyType"]) as IAnalyze.IAnalyzeStrategy;

            this.analyzeStrategy.Analyze = this.analyze;
            this.analyzeStrategy.AnalyzeBegin += new EventHandler<IAnalyze.EventArgsAnalyzeBegin>(analyzeStrategy_AnalyzeBegin);
            this.analyzeStrategy.AnalyzeAlarm += new EventHandler<IAnalyze.EventArgsAnalyzeAlarm>(analyzeStrategy_AnalyzeAlarm);
            this.analyzeStrategy.AnalyzeEnd += new EventHandler<IAnalyze.EventArgsAnalyzeEnd>(analyzeStrategy_AnalyzeEnd);

            //动态加载巡航脚本模块
            this.cruise = this.CreateType(configInfo["CruiseFile"], configInfo["CruiseType"]) as IScript.ICruiseAnalyzeScript;

            this.cruise.ScriptCallFucntion = new CallFunctionCallback(this.CallFunction);
            this.cruise.ScriptCallParameter = new CallParameterCallback(this.CallParameter);


            this.areaName = area;
            this.deviceName = device;

            //获取水平修正角
            if (!short.TryParse(this.configInfo["revise"], out this.revise))
                this.revise = 0;

            //获取经度
            if (!uint.TryParse(this.configInfo["longitude"], out this.longitude))
                longitude = Convert.ToUInt32(random.Next(5000));

            //获取纬度
            if (!uint.TryParse(this.configInfo["latitude"], out this.latitude))
                this.latitude = Convert.ToUInt32(random.Next(5000));

            //获取高度
            if (!uint.TryParse(this.configInfo["high"], out this.high))
                this.high = Convert.ToUInt32(random.Next(100, 500));

            //是否能开启透明通道
            bool.TryParse(configInfo["videotransfer"], out this.enableTransfer);

            this.ip = this.configInfo["ip"];

            if (ushort.TryParse(this.configInfo["port"], out this.port)) this.port = 8000;
            this.user = this.configInfo["user"];
            this.password = this.configInfo["password"];

            if (!byte.TryParse(this.configInfo["channel"], out this.channel))
                this.channel = 1;

            this.isInitialized = true;
            this.State |= StateType.Initial;
            OnInitialized();

            return true;
        }

        protected virtual void OnInitialized()
        {
        }

        /// <summary>
        /// 登陆编码器
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool LoginEncoder()
        {
            if (this.video.IsLogined) return true;

            this.video.LoginEncoder(ip, port, user, password);

            if (this.video.IsLogined)
            {//如果登陆成功则开启透明通道
                //开启透明通道
                if (this.enableTransfer)
                {
                    this.video.Transfer485Callback = new Transfer485Callback(GetAngle);
                    if (this.video.OpenTransfer485()) this.State |= StateType.Transfer;
                }

                this.State |= StateType.Encoder;

                OnLogined();
            }

            return this.video.IsLogined;
        }

        protected virtual void OnLogined()
        {

        }

        /// <summary>
        /// 登出编码器
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool LogoutEncoder()
        {
            this.CheckLogined(true, "LogoutEncoder");

            if (this.enableTransfer && this.video.IsTransferOpened)
                this.video.CloseTransfer485();

            this.State &= ~StateType.Encoder;

            this.video.LogoutEncoder();

            OnLogouted();

            return !this.video.IsLogined;
        }

        protected virtual void OnLogouted()
        {

        }

        /// <summary>
        /// 开启视频
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool OpenVideo()
        {
            this.CheckLogined(true, "OpenVideo");

            ////注意：此处如果正在录像或识别，可能导致其意外终止
            //if (this.video.IsOpened) this.video.CloseVideo();
            if (this.video.IsOpened) return true;

            OnVideoOpening();

            this.State |= StateType.Video;

            OnVideoOpened();

            return this.video.IsOpened;
        }

        protected virtual void OnVideoOpening()
        {
            if (this.video.OpenVideoDecode(channel, this.Handle))
            {
                if (this.AsyncGetHorizon())
                {
                    Thread.Sleep(1000);
                    if (this.horizon != 0 && this.AsyncGetVertical())
                    {
                        Thread.Sleep(1000);
                        if (this.vertical != 0 && this.ReadTrainAngle())//读入训练角度
                            this.calculateEnabled = true;
                        else
                            this.StartTrainingAngle();
                    }
                }

            }
        }
        public event EventHandler VideoOpened;
        protected virtual void OnVideoOpened()
        {
            if (this.VideoOpened != null) this.VideoOpened(this, null);
        }

        /// <summary>
        /// 关闭视频
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool CloseVideo()
        {
            this.CheckLogined(true, "CloseVideo");
            this.CheckOpened(true, "CloseVideo");

            if (!this.video.IsLogined || !this.video.IsOpened) return false;

            OnVideoClosing();

            this.State &= ~StateType.Video;

            OnVideoClosed();

            this.Invalidate();

            return !this.video.IsOpened;
        }

        protected virtual void OnVideoClosing()
        {
            this.analyzeStrategy.SyncStopAnalyzeStrategy(3000);
            this.cruise.Stop();
            if (this.video.IsRecording) this.StopRecord();
            this.video.CloseVideo();
        }
        public event EventHandler VideoClosed;
        protected virtual void OnVideoClosed()
        {
            this.calculateEnabled = false;
            this.isTrainingangle = false;

            this.tReportHorizon.Stop();
            this.tReportVertical.Stop();
            this.SWPTZStart.Stop();
            this.SWPTZStop.Stop();
            this.timeRecord = new DateTime();
            this.enableTransfer = false;
            this.State = StateType.Other;

            if (this.VideoClosed != null) VideoClosed(this, null);
        }

        /// <summary>
        /// 视频抓拍
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool SnapVideo()
        {
            this.CheckLogined(true, "SnapVideo");
            this.CheckOpened(true, "SnapVideo");

            string snap = this.configInfo["snap"];
            DateTime DT = DateTime.Now;
            string path = FilePath.GetDatePath(snap, this.areaName, this.deviceName, DT);
            if (!Directory.Exists(path)) Directory.CreateDirectory(path);
            string file = FilePath.GetDatePathTimeFile(snap, this.areaName, this.deviceName, DT, "bmp");

            return this.video.SnapVideo(file);

        }

        DateTime timeRecord = new DateTime();   //记录开始录像的时间
        /// <summary>
        /// 开始录像
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool StartRecord()
        {
            if (this.video.IsRecording || tRecord.Enabled) return true;

            this.State |= StateType.Record;


            BeginRecord();

            if (this.video.IsRecording) tRecord.Start();

            return this.video.IsRecording;

        }

        /// <summary>
        /// 开始一段新的录像
        /// </summary>
        void BeginRecord()
        {
            this.CheckLogined(true, "StartRecord");
            this.CheckOpened(true, "StartRecord");

            if (DateTime.Now.Minute >= 59 && DateTime.Now.Second > 50) return;

            string record = this.configInfo["record"];
            timeRecord = DateTime.Now;
            string file = FilePath.GetDatePathTimeFile(record, this.areaName, this.deviceName, timeRecord, "mp4");
            FileInfo FI = new FileInfo(file);
            string tempPath = FI.DirectoryName + "/temp/";
            if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath);
            string tempFile = tempPath + FI.Name;
            this.video.StartRecord(tempFile);       //将录像存储到临时文件中
        }

        /// <summary>
        /// 停止录像
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool StopRecord()
        {
            tRecord.Stop();

            if (!this.video.IsRecording) return true;

            this.State &= ~StateType.Record;

            EndRecord();

            return !this.video.IsRecording;
        }

        

        /// <summary>
        /// 结束一段录像
        /// </summary>
        void EndRecord()
        {
            this.CheckLogined(true, "StopRecord");
            this.CheckOpened(true, "StopRecord");
            this.CheckRecording(true, "StopRecord");

            this.video.StopRecord();

            string record = this.configInfo["record"];
            string fileRecord = FilePath.GetDatePathTimeFile(record, this.areaName, this.deviceName, timeRecord, "mp4");
            string fileInfo = FilePath.GetDatePathTimeFile(record, this.areaName, this.deviceName, timeRecord, "record");
            FileInfo FI = new FileInfo(fileRecord);
            string tempPath = FI.DirectoryName + "/temp/";
            string tempFile = tempPath + FI.Name;
            if (File.Exists(tempFile))
            {
                if (this.recordReader.Open(tempFile, IntPtr.Zero))
                {
                    using (StreamWriter SW = File.CreateText(fileInfo))
                    {
                        ////录像起始时间
                        //SW.WriteLine(timeRecord.Ticks);
                        //录像时长
                        SW.WriteLine(new TimeSpan((long)this.recordReader.GetFileTime() * 10000000));
                    }
                    this.recordReader.Close();
                    File.Move(tempFile, fileRecord);
                }
            }
        }

        /// <summary>
        /// 每小时的59分50秒结束一段录像
        /// 每小时的0分0秒开始一段新录像
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tRecord_Tick(object sender, EventArgs e)
        {
            DateTime DT = DateTime.Now;
            if (DT.Minute >= 59 && DT.Second > 50 && this.video.IsRecording) EndRecord();
            else if (DT.Minute < 1 && DT.Second < 10 && !this.video.IsRecording) BeginRecord();
        }

        #endregion

        #region 云镜控制
        Stopwatch SWPTZStop = new Stopwatch();
        Stopwatch SWPTZStart = new Stopwatch();

        /// <summary>
        /// 云台左转
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PanLeft(byte speed)
        {
            try
            {
                this.CheckLogined(true, "PanLeft");
                this.CheckOpened(true, "PanLeft");
            }
            catch { return false; }

            if (!StartCalculateAngle(0)) return false;

            this.panTiltSpeed = speed;


            this.State &= ~StateType.Stop;
            this.State |= StateType.Left;

            return this.video.PanLeft(this.panTiltSpeed);
        }

        /// <summary>
        /// 云台右转
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PanRight(byte speed)
        {
            try
            {
                this.CheckLogined(true, "PanRight");
                this.CheckOpened(true, "PanRight");
            }
            catch { return false; }

            if (!StartCalculateAngle(1)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Right;

            bool result = this.video.PanRight(this.panTiltSpeed);


            return result;
        }

        /// <summary>
        /// 云台上仰
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool TiltUp(byte speed)
        {
            try
            {
                this.CheckLogined(true, "TiltUp");
                this.CheckOpened(true, "TiltUp");
            }
            catch { return false; }

            if (!StartCalculateAngle(2)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Up;

            return this.video.TiltUp(this.panTiltSpeed);

        }

        /// <summary>
        /// 云台下俯
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool TiltDown(byte speed)
        {
            try
            {
                this.CheckLogined(true, "TiltDown");
                this.CheckOpened(true, "TiltDown");
            }
            catch { return false; }

            if (!StartCalculateAngle(3)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Down;

            return this.video.TiltDown(this.panTiltSpeed);

        }

        /// <summary>
        /// 云台上仰和左转
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PanLeftTiltUp(byte speed)
        {
            this.CheckLogined(true, "PanLeftTiltUp");
            this.CheckOpened(true, "PanLeftTiltUp");

            if (!StartCalculateAngle(4)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Left;
            this.State |= StateType.Up;

            return this.video.PanLeftTiltUp(this.panTiltSpeed);

        }

        /// <summary>
        /// 云台下俯和左转
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PanLeftTiltDown(byte speed)
        {
            this.CheckLogined(true, "PanLeftTiltDown");
            this.CheckOpened(true, "PanLeftTiltDown");

            if (!StartCalculateAngle(5)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Left;
            this.State |= StateType.Down;

            return this.video.PanLeftTiltDown(this.panTiltSpeed);

        }

        /// <summary>
        /// 云台上仰和右转
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PanRightTiltUp(byte speed)
        {
            this.CheckLogined(true, "PanRightTiltUp");
            this.CheckOpened(true, "PanRightTiltUp");

            if (!StartCalculateAngle(6)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Right;
            this.State |= StateType.Up;

            return this.video.PanRightTiltUp(this.panTiltSpeed);
        }

        /// <summary>
        /// 云台下俯和右转
        /// </summary>
        /// <param name="speed">云台转动的速度,范围为1到7</param>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PanRightTiltDown(byte speed)
        {
            this.CheckLogined(true, "PanRightTiltDown");
            this.CheckOpened(true, "PanRightTiltDown");

            if (!StartCalculateAngle(7)) return false;

            this.panTiltSpeed = speed;

            this.State &= ~StateType.Stop;
            this.State |= StateType.Right;
            this.State |= StateType.Down;

            return this.video.PanRightTiltDown(this.panTiltSpeed);
        }

        /// <summary>
        /// 倍率变小
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool ZoomWide()
        {
            this.CheckLogined(true, "ZoomWide");
            this.CheckOpened(true, "ZoomWide");
            this.State &= ~StateType.Stop;
            this.State |= StateType.Wide;
            return this.video.ZoomOut();
        }

        /// <summary>
        /// 倍率变大
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool ZoomTele()
        {
            this.CheckLogined(true, "ZoomTele");
            this.CheckOpened(true, "ZoomTele");
            this.State &= ~StateType.Stop;
            this.State |= StateType.Tele;
            return this.video.ZoomIn();
        }

        /// <summary>
        /// 焦点前调
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool FocusNear()
        {
            this.CheckLogined(true, "FocusNear");
            this.CheckOpened(true, "FocusNear");
            this.State &= ~StateType.Stop;
            this.State |= StateType.Near;
            return this.video.FocusNear();
        }

        /// <summary>
        /// 焦点后调
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool FocusFar()
        {
            this.CheckLogined(true, "FocusFar");
            this.CheckOpened(true, "FocusFar");
            this.State &= ~StateType.Stop;
            this.State |= StateType.Far;
            return this.video.FocusFar();
        }

        /// <summary>
        /// 光圈缩小
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool IrisClose()
        {
            this.CheckLogined(true, "IrisClose");
            this.CheckOpened(true, "IrisClose");
            this.State &= ~StateType.Stop;
            this.State |= StateType.Close;
            return this.video.IrisClose();
        }

        /// <summary>
        /// 光圈扩大
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool IrisOpen()
        {
            this.CheckLogined(true, "IrisOpen");
            this.CheckOpened(true, "IrisOpen");
            this.State &= ~StateType.Stop;
            this.State |= StateType.Open;
            return this.video.IrisOpen();
        }

        /// <summary>
        /// 云台停止转动
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool PTZStop()
        {
            try
            {
                this.CheckLogined(true, "PTZStop");
                this.CheckOpened(true, "PTZStop");
            }
            catch { return false; }

            if (this.tReportHorizon.Enabled) this.tReportHorizon.Stop();
            if (this.tReportVertical.Enabled) this.tReportVertical.Stop();
            bool result = this.video.PTZStop();
            switch (this.calculateIndex)
            {
                case 0: //左
                case 1: //右
                    this.AsyncGetHorizon();
                    break;
                case 2: //上
                case 3: //下
                    this.AsyncGetVertical();
                    break;
                case 4: //左上
                case 5: //左下
                case 6: //右上
                case 7: //右下
                    this.AsyncGetHorizon();
                    //Thread.Sleep(1000);
                    //this.AsyncGetVertical();
                    break;
                default:
                    break;
            }
            this.SWPTZStart.Stop();
            if (!this.isTrainingangle)
            {
                this.SWPTZStop.Reset();
                this.SWPTZStop.Start();
            }

            this.State &= ~StateType.Up;
            this.State &= ~StateType.Down;
            this.State &= ~StateType.Left;
            this.State &= ~StateType.Right;
            this.State &= ~StateType.Wide;
            this.State &= ~StateType.Tele;
            this.State &= ~StateType.Near;
            this.State &= ~StateType.Far;
            this.State &= ~StateType.Open;
            this.State &= ~StateType.Close;
            this.State |= StateType.Stop;

            return result;
        }
        #endregion

        #region 角度管理
        protected int calculateHorizon = 0;
        protected int calculateVertical = 0;
        int calculateIndex = 0;
        protected bool calculateEnabled = false;       //能够进行预测，（当能从前端读取角度时）

        protected int horizon;     //取值0~35999
        /// <summary>
        /// 水平角
        ///     以正东方向为0度，顺时针旋转的角度
        ///     范围0~359
        /// </summary>
        [Browsable(false)]
        public short Horizon { get { return (short)((this.horizon / 100 - this.revise + 360) % 360); } }

        protected int vertical;    //取值-7500~4500
        /// <summary>
        /// 垂直角
        ///     以水平为0度，向上为正，向下为负
        ///     范围-75~45
        /// </summary>
        [Browsable(false)]
        public short Vertical { get { return (short)(this.vertical / 100); } }

        /// <summary>
        /// 从云台返回值中获取角度
        /// </summary>
        /// <param name="data"></param>
        private void GetAngle(byte[] data)
        {
            if (this.messageIndex >= 7
                || messageIndex > 0 && message[0] != 0xFF)
                messageIndex = 0;

            foreach (var item in data) message[messageIndex++] = item;

            if (messageIndex >= 7
                && message[0] == 0xFF)
            {
                switch (message[0x03])
                {
                    case 0x59:  //水平角
                        this.horizon = System.BitConverter.ToUInt16(new byte[] { message[5], message[4] }, 0);
                        if (ReceiveHorizon != null) ReceiveHorizon(this, new HorizonEventArgs(this.Horizon));
                        break;
                    case 0x5B:  //垂直角
                        ushort vertical = System.BitConverter.ToUInt16(new byte[] { message[5], message[4] }, 0);
                        this.vertical = vertical < 18000 ?
                            Convert.ToInt16(-vertical) :
                            Convert.ToInt16(36000 - vertical);
                        if (ReceiveVertical != null) ReceiveVertical(this, new VerticalEventArgs(this.Vertical));
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// 云台移动到指定水平角
        /// </summary>
        /// <param name="angle">以正东方向为0度，顺时针旋转的角度</param>
        /// <returns></returns>
        public bool GoHorizon(int angle)
        {
            if (!this.enableTransfer || !this.video.IsTransferOpened) return false;

            angle = (short)((angle + this.revise + 360) % 360);
            this.CheckLogined(true, "GoHorizon");
            this.CheckOpened(true, "GoHorizon");
            //this.CheckTransfer485(true, "GoHorizon");

            if (angle < 0 || angle > 359)
                throw new ArgumentOutOfRangeException("Angle is out of Range. It must be between 0 and 359.|GoHorizon");

            byte[] byteAngleHorizon = System.BitConverter.GetBytes(angle * 100);
            byte addressBit = byte.Parse(this.configInfo["address"]);
            byte[] msgGoHorizon = new byte[] { 0xFF, addressBit, 0x00, 0x4B, byteAngleHorizon[1], byteAngleHorizon[2], 0x00 };
            for (int i = 1; i < 6; i++) msgGoHorizon[6] += msgGoHorizon[i];//生成校验位

            bool result = this.video.SendTransfer485(msgGoHorizon);

            if (result) this.horizon = angle * 100;

            return result;
        }

        /// <summary>
        /// 云台移动到指定垂直角
        /// </summary>
        /// <param name="angle">以水平面为0度，向上为正，向下为负</param>
        /// <returns></returns>
        public bool GoVertical(int angle)
        {
            if (!this.enableTransfer || !this.video.IsTransferOpened) return false;

            this.CheckLogined(true, "GoVertical");
            this.CheckOpened(true, "GoVertical");
            //this.CheckTransfer485(true, "GoVertical");
            int tempAngle = angle;
            if (angle < -75 || angle > 40)
                throw new ArgumentOutOfRangeException("Angle is out of Range. It must be between -75 and 40.|GoHorizon");

            angle = -angle;
            if (angle < 0) angle = angle * 100 + 36000;
            else angle *= 100;
            byte[] byteAngleHorizon = System.BitConverter.GetBytes(angle);
            byte addressBit = byte.Parse(this.configInfo["address"]);
            byte[] msgGoVerical = new byte[] { 0xFF, addressBit, 0x00, 0x4D, byteAngleHorizon[1], byteAngleHorizon[2], 0x00 };

            for (int i = 1; i < 6; i++) msgGoVerical[6] += msgGoVerical[i];//生成校验位

            bool result = this.video.SendTransfer485(msgGoVerical);

            if (result) this.vertical = tempAngle * 100;

            return result;
        }

        /// <summary>
        /// 异步获取水平角
        /// </summary>
        /// <returns></returns>
        public bool AsyncGetHorizon()
        {
            if (!this.enableTransfer || !this.video.IsTransferOpened) return false;

            this.CheckLogined(true, "AsyncGetHorizon");
            this.CheckOpened(true, "AsyncGetHorizon");

            byte addressBit = byte.Parse(this.configInfo["address"]);
            byte[] msgGetHorizon = new byte[] { 0xFF, addressBit, 0x00, 0x51, 0x00, 0x00, 0x00 };
            for (int i = 1; i < 6; i++) msgGetHorizon[6] += msgGetHorizon[i];//生成校验位
            return this.video.SendTransfer485(msgGetHorizon);
        }

        /// <summary>
        /// 异步获取垂直角
        /// </summary>
        /// <returns></returns>
        public bool AsyncGetVertical()
        {
            if (!this.enableTransfer || !this.video.IsTransferOpened) return false;

            this.CheckLogined(true, "AsyncGetVertical");
            this.CheckOpened(true, "AsyncGetVertical");

            byte addressBit = byte.Parse(this.configInfo["address"]);
            byte[] msgGetVertical = new byte[] { 0xFF, addressBit, 0x00, 0x53, 0x00, 0x00, 0x00 };
            for (int i = 1; i < 6; i++) msgGetVertical[6] += msgGetVertical[i];//生成校验位
            return this.video.SendTransfer485(msgGetVertical);
        }

        //开启角度预测
        delegate bool StartCalculateAngleCallback(int index);
        bool StartCalculateAngle(int index)
        {
            if (this.InvokeRequired)
            {
                return (bool)this.Invoke(new StartCalculateAngleCallback(StartCalculateAngle), new object[] { index });
            }
            else
            {
                this.calculateIndex = index;

                //检测停止后的时间间隔
                if (SWPTZStop.IsRunning)
                {
                    SWPTZStop.Stop();
                    if (SWPTZStop.ElapsedMilliseconds < 500) return false;
                    else SWPTZStop.Reset();
                }

                //启动角度预测
                if (!isTrainingangle && calculateEnabled)
                {
                    this.SWPTZStart.Reset();
                    this.SWPTZStart.Start();
                    switch (index)
                    {
                        case 0: //左
                        case 1: //右
                            this.tReportHorizon.Start();
                            this.calculateHorizon = 0;
                            break;
                        case 2: //上
                        case 3: //下
                            this.tReportVertical.Start();
                            this.calculateVertical = 0;
                            break;
                        case 4: //左上
                        case 5: //左下
                        case 6: //右上
                        case 7: //右下
                            this.tReportHorizon.Start();
                            this.calculateHorizon = 0;
                            this.tReportVertical.Start();
                            this.calculateVertical = 0;
                            break;
                        default:
                            break;
                    }
                }

                return true;
            }
        }

        private void tReportHorizon_Tick(object sender, EventArgs e)
        {
            int angle = (int)(this.Vas[this.panTiltSpeed - 1, 0] * SWPTZStart.ElapsedMilliseconds / 1000) % 36000;

            if (this.calculateIndex == 0 || this.calculateIndex == 4 || this.calculateIndex == 5)
                this.calculateHorizon = this.horizon - angle;
            else if (this.calculateIndex == 1 || this.calculateIndex == 6 || this.calculateIndex == 7)
                this.calculateHorizon = this.horizon + angle;
            this.calculateHorizon = (this.calculateHorizon + 36000) % 36000;    //修正
            short h = (short)((this.calculateHorizon / 100 - this.revise + 360) % 360);

            if (ReceiveHorizon != null) ReceiveHorizon(this, new HorizonEventArgs(h));
        }

        private void tReportVertical_Tick(object sender, EventArgs e)
        {
            int angle = (int)(this.Vas[this.panTiltSpeed - 1, 1] * SWPTZStart.ElapsedMilliseconds / 1000);

            if (this.calculateIndex == 2 || this.calculateIndex == 4 || this.calculateIndex == 6)
                this.calculateVertical = this.vertical + angle;
            else if (this.calculateIndex == 3 || this.calculateIndex == 5 || this.calculateIndex == 7)
                this.calculateVertical = this.vertical - angle;

            short v = (short)(this.calculateVertical / 100);
            if (ReceiveVertical != null) ReceiveVertical(this, new VerticalEventArgs(v));
        }
        #endregion

        #region 巡航识别

        /// <summary>
        /// 开始识别
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool StartAnalyze()
        {
            if (!this.cruise.IsAnalyzing)
            {
                this.CheckLogined(true, "StartAnalyze");
                this.CheckOpened(true, "StartAnalyze");

                if (!this.video.IsOpened) return false;

                this.cruise.IsAnalyzing = true;
                this.State |= StateType.Analyze;

                if (this.cruise.Start())
                    return true;
                else
                    return false;
            }

            return true;
        }

        /// <summary>
        /// 停止识别
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool StopAnalyze()
        {
            if (this.cruise.IsAnalyzing)
            {
                this.CheckLogined(true, "StopAnalyze");
                this.CheckOpened(true, "StopAnalyze");

                this.cruise.IsAnalyzing = false;
                this.State &= ~StateType.Analyze;
                this.analyzeStrategy.SyncStopAnalyzeStrategy(3000);

                return true;
            }

            return true;
        }

        /// <summary>
        /// 开始巡航
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool StartCruise()
        {
            if (!this.cruise.IsCruising)
            {
                this.CheckLogined(true, "StartAnalyze");
                this.CheckOpened(true, "StartAnalyze");

                this.cruise.IsCruising = true;
                this.State |= StateType.Cruise;

                if (this.cruise.Start())
                    return true;
                else
                    return false;
            }

            return true;
        }

        /// <summary>
        /// 停止巡航
        /// </summary>
        /// <returns>true表示成功，false表示失败</returns>
        public bool StopCruise()
        {
            if (this.cruise.IsCruising)
            {
                this.CheckLogined(true, "StartAnalyze");
                this.CheckOpened(true, "StartAnalyze");

                this.cruise.IsCruising = false;
                this.State &= ~StateType.Cruise;
                return true;
            }

            return true;
        }

        #endregion
        #endregion

        #region 角度预测
        bool isTrainingangle = false;

        /// <summary>
        /// 开始角度预测训练
        /// </summary>
        public void StartTrainingAngle()
        {
            if (this.enableTransfer && this.video != null && this.video.IsOpened && this.video.IsTransferOpened)
            {
                Thread thread = new Thread(new ParameterizedThreadStart(TrainingAngle));
                thread.Start(this);
            }
        }

        //1.通过多次采样来估计云台的当前位置
        //    分别在7种速度下控制云台运动10秒，然后停止
        //    （例如：采样 第0秒 向左 第1秒 采样 第2秒 采样 第3秒 ... 第9秒 采样 第10秒 停止）
        //    假设云台T秒后达到稳定速度，且一接收到停止指令立即停止
        //    将第0秒至第10秒的采样结果分别记为S0,S1...S10
        //    则平均角速度Va = ((S2-S1)+(S3-S2)+...+(S10-S9))/9
        //    第0秒至第1秒为加速运动，其运动距离记为D1=S1-S0，运动时间记为T
        //    则第1秒内的加速度为A1 = Math.Sqrt(2*D1/T)，其中T=1
        //    利用Va,A1,D1进行预测，并校正这几个变量：
        //        当云台运动时间t<1时，预测云台运动角度为D'=t*A1*A1/2
        //        当t>=1时，预测云台运动角度为D'=S1+Va*t;
        //        1.采样 第0秒 向左 第20秒 停止，则实际值为D = S20-S0，预测值为D'=D1+Va*(20-1)
        //            对Va进行调整，当D'>D时，减小Va，当D'<D时，增加Va，直到|D-D'|<Sigma
        //        2.采样 第0秒 向左 第500毫秒 停止 则实际值D = S500-S0，预测值为D'=t*A1*A1/2，
        //            对A1进行调整，当D'>D时，减小A1，当D'<D时，增加A1，直到|D-D'|<Sigma
        double[,] Vas = new double[7, 2];//七种速度下的水平和垂直平均角速度
        public void TrainingAngle(object state)
        {
            this.isTrainingangle = true;
            VideoBase video = state as VideoBase;
            for (byte speed = 1; speed < 8; speed++)
            {
                //double A1;      //1秒内加速度
                //double D1;      //1秒内运动角度
                //A1 = D1 = 0;
                int times = 5;
                int[] S = new int[times + 1];
                video.PTZStop();
                Thread.Sleep(1000);
                video.AsyncGetHorizon();
                Thread.Sleep(1000);
                S[0] = video.horizon;
                video.PanRight(speed);
                Thread.Sleep(1000);
                for (int i = 1; i <= times; i++)
                {
                    video.AsyncGetHorizon();
                    Thread.Sleep(1000);
                    S[i] = this.horizon;
                }
                video.PTZStop();

                int sum = 0;
                for (int i = 1; i < times; i++)
                {
                    int d = S[i + 1] - S[i];
                    sum += d >= 0 ? d : d + 36000;
                }
                //D1 = S[1] - S[0];
                //A1 = Math.Sqrt(2 * D1);
                Vas[speed - 1, 0] = sum / (times - 1);

                video.TiltUp(7);
                Thread.Sleep(5000);
                video.PTZStop();
                Thread.Sleep(1000);
                video.AsyncGetVertical();
                Thread.Sleep(1000);
                S[0] = video.vertical;
                video.TiltDown(speed);
                Thread.Sleep(1000);
                for (int i = 1; i <= times; i++)
                {
                    video.AsyncGetVertical();
                    Thread.Sleep(1000);
                    S[i] = this.vertical;
                }
                video.PTZStop();
                sum = 0;
                for (int i = 1; i < times; i++)
                {
                    int d = S[i] - S[i + 1];
                    sum += d;
                }
                //D1 = S[0] - S[1];
                //A1 = Math.Sqrt(2 * D1);
                Vas[speed - 1, 1] = sum / (times - 1);

            }
            this.panTiltSpeed = 4;
            this.WriteTrainAngle();
            this.isTrainingangle = false;
            this.calculateEnabled = true;
        }

        string pathAngle = "Angle";
        bool WriteTrainAngle()
        {
            if (!Directory.Exists(pathAngle)) Directory.CreateDirectory(pathAngle);
            if (Vas != null)
            {
                foreach (var item in Vas) if (item == 0) return false;

                using (StreamWriter SW = File.CreateText(string.Format("{0}/{1}_{2}.angle", this.pathAngle, this.areaName, this.deviceName)))
                {
                    for (int i = 0; i < 7; i++)
                    {
                        for (int j = 0; j < 2; j++)
                        {
                            SW.WriteLine(Vas[i, j]);
                        }
                    }
                }

                return true;
            }
            else return false;
        }

        bool ReadTrainAngle()
        {
            if (!Directory.Exists(pathAngle)) Directory.CreateDirectory(pathAngle);
            string filename = string.Format("{0}/{1}_{2}.angle", this.pathAngle, this.areaName, this.deviceName);
            if (!File.Exists(filename)) return false;

            bool ret = true;
            if (Vas != null)
            {
                using (StreamReader SR = File.OpenText(filename))
                {
                    for (int i = 0; i < 7; i++)
                    {
                        for (int j = 0; j < 2; j++)
                        {
                            string v = SR.ReadLine();
                            if (v == null) ret = false;
                            else if (!double.TryParse(v, out Vas[i, j])) ret = false;
                            if (!ret) break;
                        }
                        if (!ret) break;
                    }
                }
            }

            return ret;
        }
        #endregion
    }

    #region 状态类型
    /// <summary>
    /// 状态类型
    /// </summary>
    [Flags]
    public enum StateType : int
    {
        /// <summary>
        /// 视频初始化状态
        /// </summary>
        Initial = 0x00000001,
        /// <summary>
        /// 编码器登陆状态
        /// </summary>
        Encoder = 0x00000002,
        /// <summary>
        /// 视频开启状态
        /// </summary>
        Video = 0x00000004,
        /// <summary>
        /// 视频录像状态
        /// </summary>
        Record = 0x00000008,
        /// <summary>
        /// 烟火识别状态
        /// </summary>
        Analyze = 0x00000010,
        /// <summary>
        /// 巡航状态
        /// </summary>
        Cruise = 0x00000020,
        /// <summary>
        /// 抓拍状态
        /// </summary>
        Snap = 0x00000040,
        /// <summary>
        /// 透明通道状态
        /// </summary>
        Transfer = 0x00000080,
        /// <summary>
        /// 扩展按钮固定状态
        /// </summary>
        Fix = 0x00000100,
        /// <summary>
        /// 云台向上运动状态
        /// </summary>
        Up = 0x00010000,
        /// <summary>
        /// 云台向下运动状态
        /// </summary>
        Down = 0x00020000,
        /// <summary>
        /// 云台向左运动状态
        /// </summary>
        Left = 0x00040000,
        /// <summary>
        /// 云台向右运动状态
        /// </summary>
        Right = 0x00080000,
        /// <summary>
        /// 镜头倍率变低状态
        /// </summary>
        Wide = 0x00100000,
        /// <summary>
        /// 镜头倍率变高状态
        /// </summary>
        Tele = 0x00200000,
        /// <summary>
        /// 光圈放大状态
        /// </summary>
        Open = 0x00400000,
        /// <summary>
        /// 光圈缩小状态
        /// </summary>
        Close = 0x00800000,
        /// <summary>
        /// 焦距变近状态
        /// </summary>
        Near = 0x01000000,
        /// <summary>
        /// 焦距变远状态
        /// </summary>
        Far = 0x02000000,
        /// <summary>
        /// 云台镜头停止状态
        /// </summary>
        Stop = 0x04000000,
        /// <summary>
        /// 无状态
        /// </summary>
        Other = 0x00000000
    }
    #endregion
    
}
